home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright 1993, 1994, 1995, 1996, 1999, 2000 by Paul Mattes.
- * Permission to use, copy, modify, and distribute this software and its
- * documentation for any purpose and without fee is hereby granted,
- * provided that the above copyright notice appear in all copies and that
- * both that copyright notice and this permission notice appear in
- * supporting documentation.
- */
-
- /*
- * macros.c
- * This module handles string, macro and script (sms) processing.
- */
-
- #include "globals.h"
-
- #if defined(X3270_MENUS) /*[*/
- #include <X11/StringDefs.h>
- #include <X11/Xaw/Dialog.h>
- #endif /*]*/
-
- #include <sys/wait.h>
- #include <sys/signal.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <stdarg.h>
- #include "3270ds.h"
- #include "appres.h"
- #include "ctlr.h"
- #include "screen.h"
- #include "resources.h"
-
- #include "actionsc.h"
- #include "ctlrc.h"
- #include "ftc.h"
- #include "hostc.h"
- #include "kybdc.h"
- #include "macrosc.h"
- #include "menubarc.h"
- #include "popupsc.h"
- #if defined(X3270_PRINTER) /*[*/
- #include "printerc.h"
- #endif /*]*/
- #include "screenc.h"
- #include "statusc.h"
- #include "tablesc.h"
- #include "trace_dsc.h"
- #include "utilc.h"
- #include "xioc.h"
-
- #define ANSI_SAVE_SIZE 4096
-
- /* Externals */
- extern int linemode;
-
- /* Globals */
- struct macro_def *macro_defs = (struct macro_def *)NULL;
- Boolean macro_output = False;
-
- /* Statics */
- typedef struct sms {
- struct sms *next; /* next sms on the stack */
- char msc[1024]; /* input buffer */
- int msc_len; /* length of input buffer */
- char *dptr; /* data pointer (macros only) */
- enum sms_state {
- SS_IDLE, /* no command active (scripts only) */
- SS_INCOMPLETE, /* command(s) buffered and ready to run */
- SS_RUNNING, /* command executing */
- SS_KBWAIT, /* command awaiting keyboard unlock */
- SS_CONNECT_WAIT,/* command awaiting connection to complete */
- #if defined(X3270_FT) /*[*/
- SS_FT_WAIT, /* command awaiting file transfer to complete */
- #endif /*]*/
- SS_PAUSED, /* stopped in PauseScript action */
- SS_WAIT_ANSI, /* awaiting completion of Wait(ansi) */
- SS_WAIT_3270, /* awaiting completion of Wait(3270) */
- SS_WAIT_OUTPUT, /* awaiting completion of Wait(Output) */
- SS_SWAIT_OUTPUT,/* awaiting completion of Snap(Wait) */
- SS_WAIT_DISC, /* awaiting completion of Wait(Disconnect) */
- SS_WAIT, /* awaiting completion of Wait() */
- SS_EXPECTING, /* awaiting completion of Expect() */
- SS_CLOSING /* awaiting completion of Close() */
- } state;
- enum sms_type {
- ST_STRING, /* string */
- ST_MACRO, /* macro */
- ST_COMMAND, /* interactive command */
- ST_KEYMAP, /* keyboard map */
- ST_CHILD, /* child process */
- ST_PEER /* peer (external) process */
- } type;
- Boolean success;
- Boolean need_prompt;
- Boolean is_login;
- Boolean is_hex; /* flag for ST_STRING only */
- Boolean output_wait_needed;
- Boolean executing; /* recursion avoidance */
- FILE *outfile;
- int infd;
- int pid;
- unsigned long expect_id;
- unsigned long wait_id;
- } sms_t;
- #define SN ((sms_t *)NULL)
- static sms_t *sms = SN;
- static int sms_depth = 0;
-
- #if defined(X3270_TRACE) /*[*/
- static const char *sms_state_name[] = {
- "IDLE",
- "INCOMPLETE",
- "RUNNING",
- "KBWAIT",
- "CONNECT_WAIT",
- #if defined(X3270_FT) /*[*/
- "FT_WAIT",
- #endif /*]*/
- "PAUSED",
- "WAIT_ANSI",
- "WAIT_3270",
- "WAIT_OUTPUT",
- "SWAIT_OUTPUT",
- "WAIT_DISC",
- "WAIT",
- "EXPECTING",
- "CLOSING"
- };
- #endif /*]*/
-
- #if defined(X3270_MENUS) /*[*/
- static struct macro_def *macro_last = (struct macro_def *) NULL;
- #endif /*]*/
- static unsigned long stdin_id = 0L;
- static unsigned char *ansi_save_buf;
- static int ansi_save_cnt = 0;
- static int ansi_save_ix = 0;
- static char *expect_text = CN;
- static int expect_len = 0;
- static const char *st_name[] = { "String", "Macro", "Command", "KeymapAction",
- "ChildScript", "PeerScript" };
- #define ST_sNAME(s) st_name[(int)(s)->type]
- #define ST_NAME ST_sNAME(sms)
-
- static void script_prompt(Boolean success);
- static void script_input(void);
- static void sms_pop(Boolean can_exit);
- static void wait_timed_out(void);
-
- /* Macro that defines that the keyboard is locked due to user input. */
- #define KBWAIT (kybdlock & (KL_OIA_LOCKED|KL_OIA_TWAIT|KL_DEFERRED_UNLOCK))
-
- /* Macro that defines when it's safe to continue a Wait()ing sms. */
- #define CAN_PROCEED ( \
- IN_SSCP || \
- (IN_3270 && formatted && cursor_addr && !KBWAIT) || \
- (IN_ANSI && !(kybdlock & KL_AWAITING_FIRST)) \
- )
-
- /* Callbacks for state changes. */
- static void
- sms_connect(Boolean connected)
- {
- /* Hack to ensure that disconnects don't cause infinite recursion. */
- if (sms != SN && sms->executing)
- return;
-
- if (!connected) {
- while (sms != SN && sms->is_login) {
- if (sms->type == ST_CHILD && sms->pid > 0)
- (void) kill(sms->pid, SIGTERM);
- sms_pop(False);
- }
- }
- sms_continue();
- }
-
- static void
- sms_in3270(Boolean in3270)
- {
- if (in3270 || IN_SSCP)
- sms_continue();
- }
-
- /* One-time initialization. */
- void
- sms_init(void)
- {
- register_schange(ST_CONNECT, sms_connect);
- register_schange(ST_3270_MODE, sms_in3270);
- }
-
- #if defined(X3270_MENUS) /*[*/
- /* Parse the macros resource into the macro list */
- void
- macros_init(void)
- {
- char *s = CN;
- char *name, *action;
- struct macro_def *m;
- int ns;
- int ix = 1;
- static char *last_s = CN;
-
- /* Free the previous macro definitions. */
- while (macro_defs) {
- m = macro_defs->next;
- Free(macro_defs);
- macro_defs = m;
- }
- macro_defs = (struct macro_def *)NULL;
- macro_last = (struct macro_def *)NULL;
- if (last_s) {
- Free(last_s);
- last_s = CN;
- }
-
- /* Search for new ones. */
- if (PCONNECTED) {
- char *rname;
- char *space;
-
- rname = NewString(current_host);
- if ((space = strchr(rname, ' ')))
- *space = '\0';
- s = xs_buffer("%s.%s", ResMacros, rname);
- Free(rname);
- rname = s;
- s = get_resource(rname);
- Free(rname);
- }
- if (s == CN) {
- if (appres.macros == CN)
- return;
- s = NewString(appres.macros);
- } else
- s = NewString(s);
- last_s = s;
-
- while ((ns = split_dresource(&s, &name, &action)) == 1) {
- m = (struct macro_def *)Malloc(sizeof(*m));
- m->name = name;
- m->action = action;
- if (macro_last)
- macro_last->next = m;
- else
- macro_defs = m;
- m->next = (struct macro_def *)NULL;
- macro_last = m;
- ix++;
- }
- if (ns < 0) {
- char buf[256];
-
- (void) sprintf(buf, "Error in macro %d", ix);
- Warning(buf);
- }
- }
- #endif /*]*/
-
- /*
- * Enable input from a script.
- */
- static void
- script_enable(void)
- {
- if (sms->infd >= 0 && stdin_id == 0) {
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "Enabling input for %s[%d]\n",
- ST_NAME, sms_depth);
- #endif /*]*/
- stdin_id = AddInput(sms->infd, script_input);
- }
- }
-
- /*
- * Disable input from a script.
- */
- static void
- script_disable(void)
- {
- if (stdin_id != 0) {
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "Disabling input for %s[%d]\n",
- ST_NAME, sms_depth);
- #endif /*]*/
- RemoveInput(stdin_id);
- stdin_id = 0L;
- }
- }
-
- /* Allocate a new sms. */
- static sms_t *
- new_sms(enum sms_type type)
- {
- sms_t *s;
-
- s = (sms_t *)Calloc(1, sizeof(sms_t));
-
- s->state = SS_IDLE;
- s->type = type;
- s->dptr = s->msc;
- s->success = True;
- s->need_prompt = False;
- s->is_login = False;
- s->outfile = (FILE *)NULL;
- s->infd = -1;
- s->pid = -1;
- s->expect_id = 0L;
- s->wait_id = 0L;
- s->output_wait_needed = False;
- s->executing = False;
-
- return s;
- }
-
- /*
- * Push an sms definition on the stack.
- * Returns whether or not that is legal.
- */
- static Boolean
- sms_push(enum sms_type type)
- {
- sms_t *s;
-
- /* Preempt any running sms. */
- if (sms != SN) {
- /* Remove the running sms's input. */
- script_disable();
- }
-
- s = new_sms(type);
- if (sms != SN)
- s->is_login = sms->is_login; /* propagate from parent */
- s->next = sms;
- sms = s;
-
- /* Enable the abort button on the menu and the status indication. */
- if (++sms_depth == 1) {
- menubar_as_set(True);
- status_script(True);
- }
-
- if (ansi_save_buf == (unsigned char *)NULL)
- ansi_save_buf = (unsigned char *)Malloc(ANSI_SAVE_SIZE);
- return True;
- }
-
- /*
- * Add an sms definition to the _bottom_ of the stack.
- */
- static sms_t *
- sms_enqueue(enum sms_type type)
- {
- sms_t *s, *t, *t_prev = SN;
-
- /* Allocate and initialize a new structure. */
- s = new_sms(type);
-
- /* Find the bottom of the stack. */
- for (t = sms; t != SN; t = t->next)
- t_prev = t;
-
- if (t_prev == SN) { /* Empty stack. */
- s->next = sms;
- sms = s;
-
- /*
- * Enable the abort button on the menu and the status
- * line indication.
- */
- menubar_as_set(True);
- status_script(True);
- } else { /* Add to bottom. */
- s->next = SN;
- t_prev->next = s;
- }
-
- sms_depth++;
-
- if (ansi_save_buf == (unsigned char *)NULL)
- ansi_save_buf = (unsigned char *)Malloc(ANSI_SAVE_SIZE);
-
- return s;
- }
-
- /* Pop an sms definition off the stack. */
- static void
- sms_pop(Boolean can_exit)
- {
- sms_t *s;
-
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] complete\n",
- ST_NAME, sms_depth);
- #endif /*]*/
-
- /* When you pop the peer script, that's the end of x3270. */
- if (sms->type == ST_PEER && can_exit)
- x3270_exit(0);
-
- /* Remove the input event. */
- script_disable();
-
- /* Close the files. */
- if (sms->type == ST_CHILD) {
- (void) fclose(sms->outfile);
- (void) close(sms->infd);
- }
-
- /* Cancel any pending timeouts. */
- if (sms->expect_id != 0L)
- RemoveTimeOut(sms->expect_id);
- if (sms->wait_id != 0L)
- RemoveTimeOut(sms->wait_id);
-
- /* Release the memory. */
- s = sms;
- sms = s->next;
- Free((char *)s);
- sms_depth--;
-
- if (sms == SN) {
- /* Turn off the menu option. */
- menubar_as_set(False);
- status_script(False);
- } else if (KBWAIT && (int)sms->state < (int)SS_KBWAIT) {
- /* The child implicitly blocked the parent. */
- sms->state = SS_KBWAIT;
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] implicitly paused %s\n",
- ST_NAME, sms_depth, sms_state_name[sms->state]);
- #endif /*]*/
- } else if (sms->state == SS_IDLE) {
- /* The parent needs to be restarted. */
- script_enable();
- }
- }
-
- /*
- * Peer script initialization.
- *
- * Must be called after the initial call to connect to the host from the
- * command line, so that the initial state can be set properly.
- */
- void
- peer_script_init(void)
- {
- sms_t *s;
- Boolean on_top;
-
- if (!appres.scripted)
- return;
-
- if (sms == SN) {
- /* No login script running, simply push a new sms. */
- (void) sms_push(ST_PEER);
- s = sms;
- on_top = True;
- } else {
- /* Login script already running, pretend we started it. */
- s = sms_enqueue(ST_PEER);
- s->state = SS_RUNNING;
- on_top = False;
- }
-
- s->infd = fileno(stdin);
- s->outfile = stdout;
- (void) SETLINEBUF(s->outfile); /* even if it's a pipe */
-
- if (on_top) {
- if (HALF_CONNECTED ||
- (CONNECTED && (kybdlock & KL_AWAITING_FIRST)))
- s->state = SS_CONNECT_WAIT;
- else
- script_enable();
- }
- }
-
-
- /*
- * Interpret and execute a script or macro command.
- */
- enum em_stat { EM_CONTINUE, EM_PAUSE, EM_ERROR };
- static enum em_stat
- execute_command(enum iaction cause, char *s, char **np)
- {
- enum {
- ME_GND, /* before action name */
- ME_FUNCTION, /* within action name */
- ME_FUNCTIONx, /* saw whitespace after action name */
- ME_LPAREN, /* saw left paren */
- ME_P_PARM, /* paren: within unquoted parameter */
- ME_P_QPARM, /* paren: within quoted parameter */
- ME_P_BSL, /* paren: after backslash in quoted parameter */
- ME_P_PARMx, /* paren: saw whitespace after parameter */
- ME_S_PARM, /* space: within unquoted parameter */
- ME_S_QPARM, /* space: within quoted parameter */
- ME_S_BSL, /* space: after backslash in quoted parameter */
- ME_S_PARMx /* space: saw whitespace after parameter */
- } state = ME_GND;
- char c;
- char aname[64+1];
- char parm[1024+1];
- int nx = 0;
- Cardinal count = 0;
- String params[64];
- int i, any, exact;
- int failreason = 0;
- Boolean saw_paren = False;
- static const char *fail_text[] = {
- /*1*/ "Action name must begin with an alphanumeric character",
- /*2*/ "Syntax error in action name",
- /*3*/ "Syntax error, \")\" expected",
- /*4*/ "Extra data after parameters"
- };
- #define fail(n) { failreason = n; goto failure; }
- #define free_params() { \
- if (cause == IA_MACRO || cause == IA_KEYMAP || cause == IA_COMMAND) { \
- Cardinal j; \
- for (j = 0; j < count; j++) \
- Free(params[j]); \
- } \
- }
-
- parm[0] = '\0';
- params[count] = parm;
-
- while ((c = *s++)) switch (state) {
- case ME_GND:
- if (isspace(c))
- continue;
- else if (isalnum(c)) {
- state = ME_FUNCTION;
- nx = 0;
- aname[nx++] = c;
- } else
- fail(1);
- break;
- case ME_FUNCTION: /* within function name */
- if (c == '(' || isspace(c)) {
- aname[nx] = '\0';
- if (c == '(') {
- nx = 0;
- state = ME_LPAREN;
- saw_paren = True;
- } else
- state = ME_FUNCTIONx;
- } else if (isalnum(c) || c == '_' || c == '-') {
- if (nx < 64)
- aname[nx++] = c;
- } else {
- fail(2);
- }
- break;
- case ME_FUNCTIONx: /* space after function name */
- if (isspace(c))
- continue;
- else if (c == '(') {
- nx = 0;
- state = ME_LPAREN;
- } else if (c == '"') {
- nx = 0;
- state = ME_S_QPARM;
- }
- else { state = ME_S_PARM;
- nx = 0;
- parm[nx++] = c;
- }
- break;
- case ME_LPAREN:
- if (isspace(c))
- continue;
- else if (c == '"')
- state = ME_P_QPARM;
- else if (c == ',') {
- parm[nx++] = '\0';
- params[++count] = &parm[nx];
- } else if (c == ')')
- goto success;
- else {
- state = ME_P_PARM;
- parm[nx++] = c;
- }
- break;
- case ME_P_PARM:
- if (isspace(c)) {
- parm[nx++] = '\0';
- params[++count] = &parm[nx];
- state = ME_P_PARMx;
- } else if (c == ')') {
- parm[nx] = '\0';
- ++count;
- goto success;
- } else if (c == ',') {
- parm[nx++] = '\0';
- params[++count] = &parm[nx];
- state = ME_LPAREN;
- } else {
- if (nx < 1024)
- parm[nx++] = c;
- }
- break;
- case ME_P_BSL:
- if (c == 'n' && nx < 1024)
- parm[nx++] = '\n';
- else {
- if (c != '"' && nx < 1024)
- parm[nx++] = '\\';
- if (nx < 1024)
- parm[nx++] = c;
- }
- state = ME_P_QPARM;
- break;
- case ME_P_QPARM:
- if (c == '"') {
- parm[nx++] = '\0';
- params[++count] = &parm[nx];
- state = ME_P_PARMx;
- } else if (c == '\\') {
- state = ME_P_BSL;
- } else if (nx < 1024)
- parm[nx++] = c;
- break;
- case ME_P_PARMx:
- if (isspace(c))
- continue;
- else if (c == ',')
- state = ME_LPAREN;
- else if (c == ')')
- goto success;
- else
- fail(3);
- break;
- case ME_S_PARM:
- if (isspace(c)) {
- parm[nx++] = '\0';
- params[++count] = &parm[nx];
- state = ME_S_PARMx;
- } else {
- if (nx < 1024)
- parm[nx++] = c;
- }
- break;
- case ME_S_BSL:
- if (c == 'n' && nx < 1024)
- parm[nx++] = '\n';
- else {
- if (c != '"' && nx < 1024)
- parm[nx++] = '\\';
- if (nx < 1024)
- parm[nx++] = c;
- }
- state = ME_S_QPARM;
- break;
- case ME_S_QPARM:
- if (c == '"') {
- parm[nx++] = '\0';
- params[++count] = &parm[nx];
- state = ME_S_PARMx;
- } else if (c == '\\') {
- state = ME_S_BSL;
- } else if (nx < 1024)
- parm[nx++] = c;
- break;
- case ME_S_PARMx:
- if (isspace(c))
- continue;
- else if (c == '"')
- state = ME_S_QPARM;
- else {
- parm[nx++] = c;
- state = ME_S_PARM;
- }
- break;
- }
-
- /* Terminal state. */
- switch (state) {
- case ME_FUNCTION: /* mid-function-name */
- aname[nx] = '\0';
- break;
- case ME_FUNCTIONx: /* space after function */
- break;
- case ME_GND: /* nothing */
- if (np)
- *np = s - 1;
- return EM_CONTINUE;
- case ME_S_PARMx: /* space after space-style parameter */
- break;
- case ME_S_PARM: /* mid space-style parameter */
- parm[nx++] = '\0';
- params[++count] = &parm[nx];
- break;
- default:
- fail(3);
- }
-
- success:
- if (c) {
- while (*s && isspace(*s))
- s++;
- if (*s) {
- if (np)
- *np = s;
- else
- fail(4);
- } else if (np)
- *np = s;
- } else if (np)
- *np = s-1;
-
- /* If it's a macro, do variable substitutions. */
- if (cause == IA_MACRO || cause == IA_KEYMAP || cause == IA_COMMAND) {
- Cardinal j;
-
- for (j = 0; j < count; j++)
- params[j] = do_subst(params[j], True, False);
- }
-
- /* Search the action list. */
- if (!strncasecmp(aname, PA_PFX, strlen(PA_PFX))) {
- popup_an_error("Invalid action: %s", aname);
- free_params();
- return EM_ERROR;
- }
- any = -1;
- exact = -1;
- for (i = 0; i < actioncount; i++) {
- if (!strcasecmp(aname, actions[i].string)) {
- exact = any = i;
- break;
- }
- }
- if (exact < 0) {
- for (i = 0; i < actioncount; i++) {
- if (!strncasecmp(aname, actions[i].string,
- strlen(aname))) {
- if (any >= 0) {
- popup_an_error("Ambiguous action name: "
- "%s", aname);
- free_params();
- return EM_ERROR;
- }
- any = i;
- }
- }
- }
- if (any >= 0) {
- ia_cause = cause;
- (*actions[any].proc)((Widget)NULL, (XEvent *)NULL,
- count? params: (String *)NULL, &count);
- free_params();
- screen_disp();
- } else {
- popup_an_error("Unknown action: %s", aname);
- free_params();
- return EM_ERROR;
- }
-
- #if defined(X3270_FT) /*[*/
- if (ft_state != FT_NONE)
- sms->state = SS_FT_WAIT;
- #endif /*]*/
- if (KBWAIT)
- return EM_PAUSE;
- else
- return EM_CONTINUE;
-
- failure:
- popup_an_error(fail_text[failreason-1]);
- return EM_ERROR;
- #undef fail
- #undef free_params
- }
-
- /* Run the string at the top of the stack. */
- static void
- run_string(void)
- {
- int len;
- int len_left;
-
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] running\n",
- ST_NAME, sms_depth);
- #endif /*]*/
-
- sms->state = SS_RUNNING;
- len = strlen(sms->dptr);
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%sString[%d]: '%s'\n",
- sms->is_hex ? "Hex" : "",
- sms_depth, sms->dptr);
- #endif /*]*/
-
- if (sms->is_hex) {
- if (KBWAIT) {
- sms->state = SS_KBWAIT;
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef,
- "%s[%d] paused %s\n",
- ST_NAME, sms_depth,
- sms_state_name[sms->state]);
- #endif /*]*/
- } else {
- hex_input(sms->dptr);
- sms_pop(False);
- }
- } else {
- if ((len_left = emulate_input(sms->dptr, len, False))) {
- sms->dptr += len - len_left;
- if (KBWAIT) {
- sms->state = SS_KBWAIT;
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef,
- "%s[%d] paused %s\n",
- ST_NAME, sms_depth,
- sms_state_name[sms->state]);
- #endif /*]*/
- }
- } else {
- sms_pop(False);
- }
- }
- }
-
- /* Run the macro at the top of the stack. */
-
- static void
- run_macro(void)
- {
- char *a = sms->dptr;
- char *nextm;
- enum em_stat es;
- sms_t *s;
-
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] running\n",
- ST_NAME, sms_depth);
- #endif /*]*/
-
- /*
- * Keep executing commands off the line until one pauses or
- * we run out of commands.
- */
- while (*a) {
- /*
- * Check for command failure.
- */
- if (!sms->success) {
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] failed\n",
- ST_NAME, sms_depth);
- #endif /*]*/
- /* Propogate it. */
- if (sms->next != SN)
- sms->next->success = False;
- break;
- }
-
- sms->state = SS_RUNNING;
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d]: '%s'\n",
- ST_NAME, sms_depth, a);
- #endif /*]*/
- s = sms;
- s->success = True;
- s->executing = True;
- es = execute_command((s->type == ST_MACRO)? IA_MACRO:
- ((s->type == ST_COMMAND)? IA_COMMAND: IA_KEYMAP),
- a, &nextm);
- s->executing = False;
- s->dptr = nextm;
-
- /*
- * If a new sms was started, we will be resumed
- * when it completes.
- */
- if (sms != s) {
- return;
- }
-
- /* Macro could not execute. Abort it. */
- if (es == EM_ERROR) {
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] error\n",
- ST_NAME, sms_depth);
- #endif /*]*/
- /* Propogate it. */
- if (sms->next != SN)
- sms->next->success = False;
- break;
- }
-
- /* Macro paused, implicitly or explicitly. Suspend it. */
- if (es == EM_PAUSE || (int)sms->state >= (int)SS_KBWAIT) {
- if (sms->state == SS_RUNNING)
- sms->state = SS_KBWAIT;
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] paused %s\n",
- ST_NAME, sms_depth,
- sms_state_name[sms->state]);
- #endif /*]*/
- sms->dptr = nextm;
- return;
- }
-
- /* Macro ran. */
- a = nextm;
- }
-
- /* Finished with this macro. */
- sms_pop(False);
- }
-
- /* Push a macro (macro, command or keymap action) on the stack. */
- static void
- push_xmacro(enum sms_type type, char *s, Boolean is_login)
- {
- macro_output = False;
- if (!sms_push(type))
- return;
- (void) strncpy(sms->msc, s, 1023);
- sms->msc[1023] = '\0';
- sms->msc_len = strlen(s);
- if (sms->msc_len > 1023)
- sms->msc_len = 1023;
- if (is_login) {
- sms->state = SS_WAIT;
- sms->is_login = True;
- } else
- sms->state = SS_INCOMPLETE;
- if (sms_depth == 1)
- sms_continue();
- }
-
- /* Push a macro on the stack. */
- void
- push_macro(char *s, Boolean is_login)
- {
- push_xmacro(ST_MACRO, s, is_login);
- }
-
- /* Push an interactive command on the stack. */
- void
- push_command(char *s)
- {
- push_xmacro(ST_COMMAND, s, False);
- }
-
- /* Push an keymap action on the stack. */
- void
- push_keymap_action(char *s)
- {
- push_xmacro(ST_KEYMAP, s, False);
- }
-
- /* Push a string on the stack. */
- static void
- push_string(char *s, Boolean is_login, Boolean is_hex)
- {
- if (!sms_push(ST_STRING))
- return;
- (void) strncpy(sms->msc, s, 1023);
- sms->msc[1023] = '\0';
- sms->msc_len = strlen(s);
- if (sms->msc_len > 1023)
- sms->msc_len = 1023;
- if (is_login) {
- sms->state = SS_WAIT;
- sms->is_login = True;
- } else
- sms->state = SS_INCOMPLETE;
- sms->is_hex = is_hex;
- if (sms_depth == 1)
- sms_continue();
- }
-
- /* Set a pending string. */
- void
- ps_set(char *s, Boolean is_hex)
- {
- push_string(s, False, is_hex);
- }
-
- #if defined(X3270_MENUS) /*[*/
- /* Callback for macros menu. */
- void
- macro_command(struct macro_def *m)
- {
- push_macro(m->action, False);
- }
- #endif /*]*/
-
- /*
- * If the string looks like an action, e.g., starts with "Xxx(", run a login
- * macro. Otherwise, set a simple pending login string.
- */
- void
- login_macro(char *s)
- {
- char *t = s;
- Boolean looks_right = False;
-
- while (isspace(*t))
- t++;
- if (isalnum(*t)) {
- while (isalnum(*t))
- t++;
- while (isspace(*t))
- t++;
- if (*t == '(')
- looks_right = True;
- }
-
- if (looks_right)
- push_macro(s, True);
- else
- push_string(s, True, False);
- }
-
- /* Run the first command in the msc[] buffer. */
- static void
- run_script(void)
- {
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] running\n",
- ST_NAME, sms_depth);
- #endif /*]*/
-
- for (;;) {
- char *ptr;
- int cmd_len;
- char *cmd;
- sms_t *s;
- enum em_stat es;
-
- /* If the script isn't idle, we're done. */
- if (sms->state != SS_IDLE)
- break;
-
- /* If a prompt is required, send one. */
- if (sms->need_prompt) {
- script_prompt(sms->success);
- sms->need_prompt = False;
- }
-
- /* If there isn't a pending command, we're done. */
- if (!sms->msc_len)
- break;
-
- /* Isolate the command. */
- ptr = memchr(sms->msc, '\n', sms->msc_len);
- if (!ptr)
- break;
- *ptr++ = '\0';
- cmd_len = ptr - sms->msc;
- cmd = sms->msc;
-
- /* Execute it. */
- sms->state = SS_RUNNING;
- sms->success = True;
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d]: '%s'\n", ST_NAME,
- sms_depth, cmd);
- #endif /*]*/
- s = sms;
- s->executing = True;
- es = execute_command(IA_SCRIPT, cmd, (char **)NULL);
- s->executing = False;
-
- /* Move the rest of the buffer over. */
- if (cmd_len < s->msc_len) {
- s->msc_len -= cmd_len;
- (void) memmove(s->msc, ptr, s->msc_len);
- } else
- s->msc_len = 0;
-
- /*
- * If a new sms was started, we will be resumed
- * when it completes.
- */
- if (sms != s) {
- s->need_prompt = True;
- return;
- }
-
- /* Handle what it did. */
- if (es == EM_PAUSE || (int)sms->state >= (int)SS_KBWAIT) {
- if (sms->state == SS_RUNNING)
- sms->state = SS_KBWAIT;
- script_disable();
- if (sms->state == SS_CLOSING) {
- sms_pop(False);
- return;
- }
- sms->need_prompt = True;
- } else if (es == EM_ERROR) {
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] error\n",
- ST_NAME, sms_depth);
- #endif /*]*/
- script_prompt(False);
- } else
- script_prompt(sms->success);
- if (sms->state == SS_RUNNING)
- sms->state = SS_IDLE;
- else {
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "%s[%d] paused %s\n",
- ST_NAME, sms_depth,
- sms_state_name[sms->state]);
- #endif /*]*/
- }
- }
- }
-
- /* Handle an error generated during the execution of a script or macro. */
- void
- sms_error(char *msg)
- {
- /* Print the error message. */
- (void) fprintf(stderr, "%s\n", msg);
-
- /* Fail the command. */
- sms->success = False;
-
- /* Cancel any login. */
- if (sms->is_login)
- host_disconnect(True);
- }
-
- /* Generate a response to a script command. */
- void
- sms_info(const char *fmt, ...)
- {
- char *nl;
- char msgbuf[4096];
- char *msg = msgbuf;
- va_list args;
-
- va_start(args, fmt);
- vsprintf(msgbuf, fmt, args);
- va_end(args);
-
- do {
- int nc;
-
- nl = strchr(msg, '\n');
- if (nl != CN) {
- nc = nl - msg;
- } else
- nc = strlen(msg);
- if (nc) {
- switch (sms->type) {
- case ST_PEER:
- case ST_CHILD:
- (void) fprintf(sms->outfile, "data: %.*s\n",
- nc, msg);
- break;
- default:
- (void) printf("%.*s\n", nc, msg);
- break;
- }
- }
- msg = nl + 1;
- } while (nl);
-
- macro_output = True;
- }
-
- /* Process available input from a script. */
- static void
- script_input(void)
- {
- char buf[128];
- int nr;
- char *ptr;
- char c;
-
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "Input for %s[%d] %d\n",
- ST_NAME, sms_depth, sms->state);
- #endif /*]*/
-
- /* Read in what you can. */
- nr = read(sms->infd, buf, sizeof(buf));
- if (nr < 0) {
- popup_an_errno(errno, "%s[%d] read", ST_NAME, sms_depth);
- return;
- }
- if (nr == 0) { /* end of file */
- #if defined(X3270_TRACE) /*[*/
- if (toggled(DS_TRACE))
- (void) fprintf(tracef, "EOF %s[%d]\n",
- ST_NAME, sms_depth);
- #endif /*]*/
- sms_pop(True);
- sms_continue();
- return;
- }
-
- /* Append to the pending command, ignoring returns. */
- ptr = buf;
- while (nr--)
- if ((c = *ptr++) != '\r')
- sms->msc[sms->msc_len++] = c;
-
- /* Run the command(s). */
- sms->state = SS_INCOMPLETE;
- sms_continue();
- }
-
- /* Resume a paused sms, if conditions are now ripe. */
- void
- sms_continue(void)
- {
- while (True) {
- if (sms == SN)
- return;
-
- switch (sms->state) {
-
- case SS_IDLE:
- return; /* nothing to do */
-
- case SS_INCOMPLETE:
- case SS_RUNNING:
- break; /* let it proceed */
-
- case SS_KBWAIT:
- if (KBWAIT)
- return;
- break;
-
- case SS_WAIT_ANSI:
- if (IN_ANSI) {
- sms->state = SS_WAIT;
- continue;
- }
- return;
-
- case SS_WAIT_3270:
- if (IN_3270 | IN_SSCP) {
- sms->state = SS_WAIT;
- continue;
- }
- return;
-
- case SS_WAIT:
- if (!CAN_PROCEED)
- return;
- /* fall through... */
- case SS_CONNECT_WAIT:
- if (HALF_CONNECTED ||
- (CONNECTED && (kybdlock & KL_AWAITING_FIRST)))
- return;
- if (!CONNECTED) {
- /* connection failed */
- if (sms->need_prompt) {
- script_prompt(False);
- sms->need_prompt = False;
- }
- break;
- }
- break;
-
- #if defined(X3270_FT) /*[*/
- case SS_FT_WAIT:
- if (ft_state == FT_NONE)
- break;
- else
- return;
- #endif /*]*/
-
- case SS_WAIT_OUTPUT:
- case SS_SWAIT_OUTPUT:
- return;
-
- case SS_WAIT_DISC:
- if (!CONNECTED)
- break;
- else
- return;
-
- case SS_PAUSED:
- return;
-
- case SS_EXPECTING:
- return;
-
- case SS_CLOSING:
- return; /* can't happen, I hope */
-
- }
-
- /* Restart the sms. */
-
- sms->state = SS_IDLE;
-
- if (sms->wait_id != 0L) {
- RemoveTimeOut(sms->wait_id);
- sms->wait_id = 0L;
- }
-
- switch (sms->type) {
- case ST_STRING:
- run_string();
- break;
- case ST_MACRO:
- case ST_COMMAND:
- case ST_KEYMAP:
- run_macro();
- break;
- case ST_PEER:
- case ST_CHILD:
- script_enable();
- run_script();
- break;
- }
- }
- }
-
- /*
- * Macro- and script-specific actions.
- */
-
- static void
- dump_range(int first, int len, Boolean in_ascii, unsigned char *buf,
- int rel_rows unused, int rel_cols)
- {
- register int i;
- Boolean any = False;
- char *linebuf;
- char *s;
-
- linebuf = Malloc(maxCOLS * 3 + 1);
- s = linebuf;
-
- /*
- * If the client has looked at the live screen, then if they later
- * execute 'Wait(output)', they will need to wait for output from the
- * host. output_wait_needed is cleared by sms_host_output,
- * which is called from the write logic in ctlr.c.
- */
- if (sms != SN && buf == screen_buf)
- sms->output_wait_needed = True;
-
- for (i = 0; i < len; i++) {
- unsigned char c;
-
- if (i && !((first + i) % rel_cols)) {
- *s = '\0';
- action_output(linebuf);
- s = linebuf;
- any = False;
- }
- if (!any)
- any = True;
- if (in_ascii) {
- c = cg2asc[buf[first + i]];
- s += sprintf(s, "%c", c ? c : ' ');
- } else {
- s += sprintf(s, "%s%02x",
- i ? " " : "",
- cg2ebc[buf[first + i]]);
- }
- }
- if (any) {
- *s = '\0';
- action_output(linebuf);
- }
- Free(linebuf);
- }
-
- static void
- dump_fixed(String params[], Cardinal count, const char *name, Boolean in_ascii,
- unsigned char *buf, int rel_rows, int rel_cols, int caddr)
- {
- int row, col, len, rows = 0, cols = 0;
-
- switch (count) {
- case 0: /* everything */
- row = 0;
- col = 0;
- len = rel_rows*rel_cols;
- break;
- case 1: /* from cursor, for n */
- row = caddr / rel_cols;
- col = caddr % rel_cols;
- len = atoi(params[0]);
- break;
- case 3: /* from (row,col), for n */
- row = atoi(params[0]);
- col = atoi(params[1]);
- len = atoi(params[2]);
- break;
- case 4: /* from (row,col), for rows x cols */
- row = atoi(params[0]);
- col = atoi(params[1]);
- rows = atoi(params[2]);
- cols = atoi(params[3]);
- len = 0;
- break;
- default:
- popup_an_error("%s requires 0, 1, 3 or 4 arguments", name);
- return;
- }
-
- if (
- (row < 0 || row > rel_rows || col < 0 || col > rel_cols || len < 0) ||
- ((count < 4) && ((row * rel_cols) + col + len > rel_rows * rel_cols)) ||
- ((count == 4) && (cols < 0 || rows < 0 ||
- col + cols > rel_cols || row + rows > rel_rows))
- ) {
- popup_an_error("%s: Invalid argument", name);
- return;
- }
- if (count < 4)
- dump_range((row * rel_cols) + col, len, in_ascii, buf,
- rel_rows, rel_cols);
- else {
- int i;
-
- for (i = 0; i < rows; i++)
- dump_range(((row+i) * rel_cols) + col, cols, in_ascii,
- buf, rel_rows, rel_cols);
- }
- }
-
- static void
- dump_field(Cardinal count, const char *name, Boolean in_ascii)
- {
- unsigned char *fa;
- int start, baddr;
- int len = 0;
-
- if (count != 0) {
- popup_an_error("%s requires 0 arguments", name);
- return;
- }
- if (!formatted) {
- popup_an_error("%s: Screen is not formatted", name);
- return;
- }
- fa = get_field_attribute(cursor_addr);
- start = fa - screen_buf;
- INC_BA(start);
- baddr = start;
- do {
- if (IS_FA(screen_buf[baddr]))
- break;
- len++;
- INC_BA(baddr);
- } while (baddr != start);
- dump_range(start, len, in_ascii, screen_buf, ROWS, COLS);
- }
-
- void
- Ascii_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- dump_fixed(params, *num_params, action_name(Ascii_action), True,
- screen_buf, ROWS, COLS, cursor_addr);
- }
-
- void
- AsciiField_action(Widget w unused, XEvent *event unused, String *params unused,
- Cardinal *num_params)
- {
- dump_field(*num_params, action_name(AsciiField_action), True);
- }
-
- void
- Ebcdic_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- dump_fixed(params, *num_params, action_name(Ebcdic_action), False,
- screen_buf, ROWS, COLS, cursor_addr);
- }
-
- void
- EbcdicField_action(Widget w unused, XEvent *event unused,
- String *params unused, Cardinal *num_params)
- {
- dump_field(*num_params, action_name(EbcdicField_action), False);
- }
-
- /*
- * The sms prompt is preceeded by a status line with 11 fields:
- *
- * 1 keyboard status
- * U unlocked
- * L locked, waiting for host response
- * E locked, keying error
- * 2 formatting status of screen
- * F formatted
- * U unformatted
- * 3 protection status of current field
- * U unprotected (modifiable)
- * P protected
- * 4 connect status
- * N not connected
- * C(host) connected
- * 5 emulator mode
- * N not connected
- * C connected in ANSI character mode
- * L connected in ANSI line mode
- * P 3270 negotiation pending
- * I connected in 3270 mode
- * 6 model number
- * 7 rows
- * 8 cols
- * 9 cursor row
- * 10 cursor col
- * 11 main window id
- */
- static char *
- status_string(void)
- {
- char kb_stat;
- char fmt_stat;
- char prot_stat;
- char *connect_stat = CN;
- char em_mode;
- char s[1024];
- char *r;
-
- if (!kybdlock)
- kb_stat = 'U';
- else if (!CONNECTED || KBWAIT)
- kb_stat = 'L';
- else
- kb_stat = 'E';
-
- if (formatted)
- fmt_stat = 'F';
- else
- fmt_stat = 'U';
-
- if (!formatted)
- prot_stat = 'U';
- else {
- unsigned char *fa;
-
- fa = get_field_attribute(cursor_addr);
- if (FA_IS_PROTECTED(*fa))
- prot_stat = 'P';
- else
- prot_stat = 'U';
- }
-
- if (CONNECTED)
- connect_stat = xs_buffer("C(%s)", current_host);
- else
- connect_stat = NewString("N");
-
- if (CONNECTED) {
- if (IN_ANSI) {
- if (linemode)
- em_mode = 'L';
- else
- em_mode = 'C';
- } else if (IN_3270)
- em_mode = 'I';
- else
- em_mode = 'P';
- } else
- em_mode = 'N';
-
- (void) sprintf(s,
- "%c %c %c %s %c %d %d %d %d %d 0x%lx",
- kb_stat,
- fmt_stat,
- prot_stat,
- connect_stat,
- em_mode,
- model_num,
- ROWS, COLS,
- cursor_addr / COLS, cursor_addr % COLS,
- #if defined(X3270_DISPLAY) /*[*/
- XtWindow(toplevel)
- #else /*][*/
- 0L
- #endif /*]*/
- );
-
- r = NewString(s);
- Free(connect_stat);
- return r;
- }
-
- static void
- script_prompt(Boolean success)
- {
- char *s;
-
- s = status_string();
- (void) fprintf(sms->outfile, "%s\n%s\n", s, success ? "ok" : "error");
- (void) fflush(sms->outfile);
- Free(s);
- }
-
- /* Save the state of the screen for Snap queries. */
- static char *snap_status = NULL;
- static unsigned char *snap_buf = NULL;
- static int snap_rows = 0;
- static int snap_cols = 0;
- static int snap_field_start = -1;
- static int snap_field_length = -1;
- static int snap_caddr = 0;
-
- static void
- snap_save(void)
- {
- sms->output_wait_needed = True;
- if (snap_status != NULL)
- Free(snap_status);
- snap_status = status_string();
-
- if (snap_buf != NULL)
- Free(snap_buf);
- snap_buf = (unsigned char *)Malloc(ROWS*COLS);
- (void) memcpy(snap_buf, screen_buf, ROWS*COLS);
-
- snap_rows = ROWS;
- snap_cols = COLS;
-
- if (!formatted) {
- snap_field_start = -1;
- snap_field_length = -1;
- } else {
- unsigned char *fa;
- int baddr;
-
- snap_field_length = 0;
- fa = get_field_attribute(cursor_addr);
- snap_field_start = fa - screen_buf;
- INC_BA(snap_field_start);
- baddr = snap_field_start;
- do {
- if (IS_FA(screen_buf[baddr]))
- break;
- snap_field_length++;
- INC_BA(baddr);
- } while (baddr != snap_field_start);
- }
- snap_caddr = cursor_addr;
- }
-
- /*
- * "Snap" action, maintains a snapshot for consistent multi-field comparisons:
- *
- * Snap [Save]
- * updates the saved image from the live image
- * Snap Rows
- * returns the number of rows
- * Snap Cols
- * returns the number of columns
- * Snap Staus
- * Snap Ascii ...
- * Snap AsciiField (not yet)
- * Snap Ebcdic ...
- * Snap EbcdicField (not yet)
- * runs the named command
- * Snap Wait [tmo] Output
- * wait for the screen to change, then do a Snap Save
- */
- void
- Snap_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- if (sms == SN || sms->state != SS_RUNNING) {
- popup_an_error("%s can only be called from scripts or macros",
- action_name(Snap_action));
- return;
- }
-
- if (*num_params == 0) {
- snap_save();
- return;
- }
-
- /* Handle 'Snap Wait' separately. */
- if (!strcasecmp(params[0], action_name(Wait_action))) {
- unsigned long tmo;
- char *ptr;
- int maxp = 0;
-
- if (*num_params > 1 &&
- (tmo = strtoul(params[1], &ptr, 10)) > 0 &&
- ptr != params[0] &&
- *ptr == '\0') {
- maxp = 3;
- } else {
- tmo = 0L;
- maxp = 2;
- }
- if (*num_params > maxp) {
- popup_an_error("Too many arguments to %s %s",
- action_name(Snap_action),
- action_name(Wait_action));
- return;
- }
- if (*num_params < maxp) {
- popup_an_error("Too few arguments to %s %s",
- action_name(Snap_action),
- action_name(Wait_action));
- return;
- }
- if (strcasecmp(params[*num_params - 1], "Output")) {
- popup_an_error("Unknown parameter to %s %s",
- action_name(Snap_action),
- action_name(Wait_action));
- return;
- }
-
- /* Must be connected. */
- if (!(CONNECTED || HALF_CONNECTED)) {
- popup_an_error("%s: Not connected",
- action_name(Snap_action));
- return;
- }
-
- /*
- * Make sure we need to wait.
- * If we don't, then Snap(Wait) is equivalent to Snap().
- */
- if (!sms->output_wait_needed) {
- snap_save();
- return;
- }
-
- /* Set the new state. */
- sms->state = SS_SWAIT_OUTPUT;
-
- /* Set up a timeout, if they want one. */
- if (tmo)
- sms->wait_id = AddTimeOut(tmo * 1000, wait_timed_out);
- return;
- }
-
- if (!strcasecmp(params[0], "Save")) {
- if (*num_params != 1) {
- popup_an_error("Extra argument(s)");
- return;
- }
- snap_save();
- } else if (!strcasecmp(params[0], "Status")) {
- if (*num_params != 1) {
- popup_an_error("Extra argument(s)");
- return;
- }
- if (snap_status == NULL) {
- popup_an_error("No saved state");
- return;
- }
- action_output("%s", snap_status);
- } else if (!strcasecmp(params[0], "Rows")) {
- if (*num_params != 1) {
- popup_an_error("Extra argument(s)");
- return;
- }
- if (snap_status == NULL) {
- popup_an_error("No saved state");
- return;
- }
- action_output("%d", snap_rows);
- } else if (!strcasecmp(params[0], "Cols")) {
- if (*num_params != 1) {
- popup_an_error("Extra argument(s)");
- return;
- }
- if (snap_status == NULL) {
- popup_an_error("No saved state");
- return;
- }
- action_output("%d", snap_cols);
- } else if (!strcasecmp(params[0], action_name(Ascii_action))) {
- if (snap_status == NULL) {
- popup_an_error("No saved state");
- return;
- }
- dump_fixed(params + 1, *num_params - 1,
- action_name(Ascii_action), True, snap_buf,
- snap_rows, snap_cols, snap_caddr);
- } else if (!strcasecmp(params[0], action_name(Ebcdic_action))) {
- if (snap_status == NULL) {
- popup_an_error("No saved state");
- return;
- }
- dump_fixed(params + 1, *num_params - 1,
- action_name(Ebcdic_action), False, snap_buf,
- snap_rows, snap_cols, snap_caddr);
- } else {
- popup_an_error("%s: Argument must be Save, Status, Rows, Cols, "
- "%s, %s or %s",
- action_name(Snap_action),
- action_name(Wait_action),
- action_name(Ascii_action),
- action_name(Ebcdic_action));
- }
- }
-
- /*
- * Wait for various conditions.
- */
- void
- Wait_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- enum sms_state next_state = SS_WAIT;
- unsigned long tmo = 0;
- char *ptr;
- Cardinal np;
- String *pr;
-
- /* Pick off the timeout parameter first. */
- if (*num_params > 0 &&
- (tmo = strtoul(params[0], &ptr, 10)) > 0 &&
- ptr != params[0] &&
- *ptr == '\0') {
- np = *num_params - 1;
- pr = params + 1;
- } else {
- np = *num_params;
- pr = params;
- }
-
- if (check_usage(Wait_action, np, 0, 1) < 0)
- return;
- if (sms == SN || sms->state != SS_RUNNING) {
- popup_an_error("%s can only be called from scripts or macros",
- action_name(Wait_action));
- return;
- }
- if (np == 1) {
- if (!strcasecmp(pr[0], "NVTMode") ||
- !strcasecmp(pr[0], "ansi")) {
- if (!IN_ANSI)
- next_state = SS_WAIT_ANSI;
- } else if (!strcasecmp(pr[0], "3270Mode") ||
- !strcasecmp(pr[0], "3270")) {
- if (!IN_3270)
- next_state = SS_WAIT_3270;
- } else if (!strcasecmp(pr[0], "Output")) {
- if (sms->output_wait_needed)
- next_state = SS_WAIT_OUTPUT;
- else
- return;
- } else if (!strcasecmp(pr[0], "Disconnect")) {
- if (CONNECTED)
- next_state = SS_WAIT_DISC;
- else
- return;
- } else if (strcasecmp(pr[0], "InputField")) {
- popup_an_error("%s argument must be InputField, "
- "NVTmode, 3270Mode, Output or Disconnect",
- action_name(Wait_action));
- return;
- }
- }
- if (!(CONNECTED || HALF_CONNECTED)) {
- popup_an_error("%s: Not connected", action_name(Wait_action));
- return;
- }
-
- /* Is it already okay? */
- if (next_state == SS_WAIT && CAN_PROCEED)
- return;
-
- /* No, wait for it to happen. */
- sms->state = next_state;
-
- /* Set up a timeout, if they want one. */
- if (tmo)
- sms->wait_id = AddTimeOut(tmo * 1000, wait_timed_out);
- }
-
- /*
- * Callback from Connect() and Reconnect() actions, to minimally pause a
- * running sms.
- */
- void
- sms_connect_wait(void)
- {
- if (sms != SN &&
- (int)sms->state >= (int)SS_RUNNING &&
- sms->state != SS_WAIT) {
- if (HALF_CONNECTED ||
- (CONNECTED && (kybdlock & KL_AWAITING_FIRST)))
- sms->state = SS_CONNECT_WAIT;
- }
- }
-
- /*
- * Callback from ctlr.c, to indicate that the host has changed the screen.
- */
- void
- sms_host_output(void)
- {
- if (sms != SN) {
- sms->output_wait_needed = False;
-
- switch (sms->state) {
- case SS_SWAIT_OUTPUT:
- snap_save();
- /* fall through... */
- case SS_WAIT_OUTPUT:
- sms->state = SS_RUNNING;
- sms_continue();
- break;
- default:
- break;
- }
- }
- }
-
- /* Return whether error pop-ups should be short-circuited. */
- Boolean
- sms_redirect(void)
- {
- return sms != SN &&
- (sms->type == ST_CHILD || sms->type == ST_PEER) &&
- (sms->state == SS_RUNNING || sms->state == SS_CONNECT_WAIT ||
- sms->wait_id != 0L);
- }
-
- #if defined(X3270_MENUS) || defined(C3270) /*[*/
- /* Return whether any scripts are active. */
- Boolean
- sms_active(void)
- {
- return sms != SN;
- }
- #endif /*]*/
-
- /* Translate an expect string (uses C escape syntax). */
- static void
- expand_expect(char *s)
- {
- char *t = Malloc(strlen(s) + 1);
- char c;
- enum { XS_BASE, XS_BS, XS_O, XS_X } state = XS_BASE;
- int n = 0;
- int nd = 0;
- static char hexes[] = "0123456789abcdef";
-
- expect_text = t;
-
- while ((c = *s++)) {
- switch (state) {
- case XS_BASE:
- if (c == '\\')
- state = XS_BS;
- else
- *t++ = c;
- break;
- case XS_BS:
- switch (c) {
- case 'x':
- nd = 0;
- n = 0;
- state = XS_X;
- break;
- case 'r':
- *t++ = '\r';
- state = XS_BASE;
- break;
- case 'n':
- *t++ = '\n';
- state = XS_BASE;
- break;
- case 'b':
- *t++ = '\b';
- state = XS_BASE;
- break;
- default:
- if (c >= '0' && c <= '7') {
- nd = 1;
- n = c - '0';
- state = XS_O;
- } else {
- *t++ = c;
- state = XS_BASE;
- }
- break;
- }
- break;
- case XS_O:
- if (nd < 3 && c >= '0' && c <= '7') {
- n = (n * 8) + (c - '0');
- nd++;
- } else {
- *t++ = n;
- *t++ = c;
- state = XS_BASE;
- }
- break;
- case XS_X:
- if (isxdigit(c)) {
- n = (n * 16) +
- strchr(hexes, tolower(c)) - hexes;
- nd++;
- } else {
- if (nd)
- *t++ = n;
- else
- *t++ = 'x';
- *t++ = c;
- state = XS_BASE;
- }
- break;
- }
- }
- expect_len = t - expect_text;
- }
-
- /* 'mem' version of strstr */
- static char *
- memstr(char *s1, char *s2, int n1, int n2)
- {
- int i;
-
- for (i = 0; i <= n1 - n2; i++, s1++)
- if (*s1 == *s2 && !memcmp(s1, s2, n2))
- return s1;
- return CN;
- }
-
- /* Check for a match against an expect string. */
- static Boolean
- expect_matches(void)
- {
- int ix, i;
- unsigned char buf[ANSI_SAVE_SIZE];
- char *t;
-
- ix = (ansi_save_ix + ANSI_SAVE_SIZE - ansi_save_cnt) % ANSI_SAVE_SIZE;
- for (i = 0; i < ansi_save_cnt; i++) {
- buf[i] = ansi_save_buf[(ix + i) % ANSI_SAVE_SIZE];
- }
- t = memstr((char *)buf, expect_text, ansi_save_cnt, expect_len);
- if (t != CN) {
- ansi_save_cnt -= ((unsigned char *)t - buf) + expect_len;
- Free(expect_text);
- expect_text = CN;
- return True;
- } else
- return False;
- }
-
- /* Store an ANSI character for use by the Ansi action. */
- void
- sms_store(unsigned char c)
- {
- if (sms == SN)
- return;
-
- /* Save the character in the buffer. */
- ansi_save_buf[ansi_save_ix++] = c;
- ansi_save_ix %= ANSI_SAVE_SIZE;
- if (ansi_save_cnt < ANSI_SAVE_SIZE)
- ansi_save_cnt++;
-
- /* If a script or macro is waiting to match a string, check now. */
- if (sms->state == SS_EXPECTING && expect_matches()) {
- RemoveTimeOut(sms->expect_id);
- sms->expect_id = 0L;
- sms->state = SS_INCOMPLETE;
- sms_continue();
- }
- }
-
- /* Dump whatever ANSI data has been sent by the host since last called. */
- void
- AnsiText_action(Widget w unused, XEvent *event unused, String *params unused,
- Cardinal *num_params unused)
- {
- register int i;
- int ix;
- unsigned char c;
- char linebuf[ANSI_SAVE_SIZE * 4 + 1];
- char *s = linebuf;
-
- if (!ansi_save_cnt)
- return;
- ix = (ansi_save_ix + ANSI_SAVE_SIZE - ansi_save_cnt) % ANSI_SAVE_SIZE;
- for (i = 0; i < ansi_save_cnt; i++) {
- c = ansi_save_buf[(ix + i) % ANSI_SAVE_SIZE];
- if (!(c & ~0x1f)) switch (c) {
- case '\n':
- s += sprintf(s, "\\n");
- break;
- case '\r':
- s += sprintf(s, "\\r");
- break;
- case '\b':
- s += sprintf(s, "\\b");
- break;
- default:
- s += sprintf(s, "\\%03o", c);
- break;
- } else if (c == '\\')
- s += sprintf(s, "\\\\");
- else
- *s++ = (char)c;
- }
- *s = '\0';
- action_output(linebuf);
- ansi_save_cnt = 0;
- ansi_save_ix = 0;
- }
-
- /* Pause a script. */
- void
- PauseScript_action(Widget w unused, XEvent *event unused, String *params unused,
- Cardinal *num_params unused)
- {
- if (sms == SN || (sms->type != ST_PEER && sms->type != ST_CHILD)) {
- popup_an_error("%s can only be called from a script",
- action_name(PauseScript_action));
- return;
- }
- sms->state = SS_PAUSED;
- }
-
- /* Continue a script. */
- void
- ContinueScript_action(Widget w, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- if (check_usage(ContinueScript_action, *num_params, 1, 1) < 0)
- return;
-
- /*
- * If this is a nested script, this action aborts the current script,
- * then applies to the previous one.
- */
- if (w == (Widget)NULL && sms_depth > 1)
- sms_pop(False);
-
- /* Continue the previous script. */
- if (sms == SN || sms->state != SS_PAUSED) {
- popup_an_error("%s: No script waiting",
- action_name(ContinueScript_action));
- sms_continue();
- return;
- }
- action_output("%s", params[0]);
- sms->state = SS_RUNNING;
- sms_continue();
- }
-
- /* Stop listening to stdin. */
- void
- CloseScript_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- if (sms != SN &&
- (sms->type == ST_PEER || sms->type == ST_CHILD)) {
-
- /* Close this script. */
- sms->state = SS_CLOSING;
- script_prompt(True);
-
- /* If nonzero status passed, fail the calling script. */
- if (*num_params > 0 &&
- atoi(params[0]) != 0 &&
- sms->next != SN) {
- sms->next->success = False;
- if (sms->is_login)
- host_disconnect(True);
- }
- } else
- popup_an_error("%s can only be called from a script",
- action_name(CloseScript_action));
- }
-
- /* Execute an arbitrary shell command. */
- void
- Execute_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- if (check_usage(Execute_action, *num_params, 1, 1) < 0)
- return;
- (void) system(params[0]);
- }
-
- /* Timeout for Expect action. */
- static void
- expect_timed_out(void)
- {
- if (sms == SN || sms->state != SS_EXPECTING)
- return;
-
- Free(expect_text);
- expect_text = CN;
- popup_an_error("%s: Timed out", action_name(Expect_action));
- sms->expect_id = 0L;
- sms->state = SS_INCOMPLETE;
- sms->success = False;
- if (sms->is_login)
- host_disconnect(True);
- sms_continue();
- }
-
- /* Timeout for Wait action. */
- static void
- wait_timed_out(void)
- {
- /* Pop up the error message. */
- popup_an_error("%s: Timed out", action_name(Wait_action));
-
- /* Forget the ID. */
- sms->wait_id = 0L;
-
- /* If this is a login macro, it has failed. */
- if (sms->is_login)
- host_disconnect(True);
-
- sms->success = False;
- sms->state = SS_INCOMPLETE;
-
- /* Let the script proceed. */
- sms_continue();
- }
-
- /* Wait for a string from the host (ANSI mode only). */
- void
- Expect_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- int tmo;
-
- /* Verify the environment and parameters. */
- if (sms == SN || sms->state != SS_RUNNING) {
- popup_an_error("%s can only be called from a script or macro",
- action_name(Expect_action));
- return;
- }
- if (check_usage(Expect_action, *num_params, 1, 2) < 0)
- return;
- if (!IN_ANSI) {
- popup_an_error("%s is valid only when connected in ANSI mode",
- action_name(Expect_action));
- }
- if (*num_params == 2) {
- tmo = atoi(params[1]);
- if (tmo < 1 || tmo > 600) {
- popup_an_error("%s: Invalid timeout: %s",
- action_name(Expect_action), params[1]);
- return;
- }
- } else
- tmo = 30;
-
- /* See if the text is there already; if not, wait for it. */
- expand_expect(params[0]);
- if (!expect_matches()) {
- sms->expect_id = AddTimeOut(tmo * 1000, expect_timed_out);
- sms->state = SS_EXPECTING;
- }
- /* else allow sms to proceed */
- }
-
-
- #if defined(X3270_MENUS) /*[*/
-
- /* "Execute an Action" menu option */
-
- static Widget execute_action_shell = (Widget)NULL;
-
- /* Callback for "OK" button on execute action popup */
- static void
- execute_action_callback(Widget w unused, XtPointer client_data,
- XtPointer call_data unused)
- {
- char *text;
-
- text = XawDialogGetValueString((Widget)client_data);
- XtPopdown(execute_action_shell);
- if (!text)
- return;
- push_macro(text, False);
- }
-
- void
- execute_action_option(Widget w unused, XtPointer client_data unused,
- XtPointer call_data unused)
- {
- if (execute_action_shell == NULL)
- execute_action_shell = create_form_popup("ExecuteAction",
- execute_action_callback, (XtCallbackProc)NULL, FORM_NO_CC);
-
- popup_popup(execute_action_shell, XtGrabExclusive);
- }
-
- #endif /*]*/
-
- /* "Script" action, runs a script as a child process. */
- void
- Script_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- int inpipe[2];
- int outpipe[2];
-
- if (*num_params < 1) {
- popup_an_error("%s requires at least one argument",
- action_name(Script_action));
- return;
- }
-
- /* Create a new script description. */
- if (!sms_push(ST_CHILD))
- return;
-
- /*
- * Create pipes and stdout stream for the script process.
- * inpipe[] is read by x3270, written by the script
- * outpipe[] is written by x3270, read by the script
- */
- if (pipe(inpipe) < 0) {
- sms_pop(False);
- popup_an_error("pipe() failed");
- return;
- }
- if (pipe(outpipe) < 0) {
- (void) close(inpipe[0]);
- (void) close(inpipe[1]);
- sms_pop(False);
- popup_an_error("pipe() failed");
- return;
- }
- if ((sms->outfile = fdopen(outpipe[1], "w")) == (FILE *)NULL) {
- (void) close(inpipe[0]);
- (void) close(inpipe[1]);
- (void) close(outpipe[0]);
- (void) close(outpipe[1]);
- sms_pop(False);
- popup_an_error("fdopen() failed");
- return;
- }
- (void) SETLINEBUF(sms->outfile);
-
- /* Fork and exec the script process. */
- if ((sms->pid = fork()) < 0) {
- (void) close(inpipe[0]);
- (void) close(inpipe[1]);
- (void) close(outpipe[0]);
- sms_pop(False);
- popup_an_error("fork() failed");
- return;
- }
-
- /* Child processing. */
- if (sms->pid == 0) {
- char **argv;
- Cardinal i;
- char env_buf[2][32];
-
- /* Clean up the pipes. */
- (void) close(outpipe[1]);
- (void) close(inpipe[0]);
-
- /* Export the names of the pipes into the environment. */
- (void) sprintf(env_buf[0], "X3270OUTPUT=%d", outpipe[0]);
- (void) putenv(env_buf[0]);
- (void) sprintf(env_buf[1], "X3270INPUT=%d", inpipe[1]);
- (void) putenv(env_buf[1]);
-
- /* Set up arguments. */
- argv = (char **)Malloc((*num_params + 1) * sizeof(char *));
- for (i = 0; i < *num_params; i++)
- argv[i] = params[i];
- argv[i] = CN;
-
- /* Exec. */
- (void) execvp(params[0], argv);
- (void) fprintf(stderr, "exec(%s) failed\n", params[0]);
- (void) _exit(1);
- }
-
- /* Clean up our ends of the pipes. */
- sms->infd = inpipe[0];
- (void) close(inpipe[1]);
- (void) close(outpipe[0]);
-
- /* Enable input. */
- script_enable();
-
- /* Set up to reap the child's exit status. */
- ++children;
- }
-
- /* "Macro" action, explicitly invokes a named macro. */
- void
- Macro_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- struct macro_def *m;
-
- if (check_usage(Script_action, *num_params, 1, 1) < 0)
- return;
- for (m = macro_defs; m != (struct macro_def *)NULL; m = m->next) {
- if (!strcmp(m->name, params[0])) {
- push_macro(m->action, False);
- return;
- }
- }
- popup_an_error("no such macro: '%s'", params[0]);
- }
-
- #if defined(X3270_PRINTER) /*[*/
- /* "Printer" action, starts or stops a printer session. */
- void
- Printer_action(Widget w unused, XEvent *event unused, String *params,
- Cardinal *num_params)
- {
- if (check_usage(Printer_action, *num_params, 1, 2) < 0)
- return;
- if (!strcasecmp(params[0], "Start")) {
- printer_start((*num_params > 1)? params[1] : CN);
- } else if (!strcasecmp(params[0], "Stop")) {
- if (*num_params != 1) {
- popup_an_error("%s: Extra argument(s)",
- action_name(Printer_action));
- return;
- }
- printer_stop();
- } else {
- popup_an_error("%s: Argument must Start or Stop",
- action_name(Printer_action));
- }
- }
- #endif /*]*/
-
- #if defined(X3270_MENUS) /*[*/
- /* Abort all running scripts. */
- void
- abort_script(void)
- {
- while (sms != SN) {
- if (sms->type == ST_CHILD && sms->pid > 0)
- (void) kill(sms->pid, SIGTERM);
- sms_pop(True);
- }
- }
- #endif /*]*/
-